﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;

namespace FerrScript
{
    static class BuiltinFunctions
    {
        static ScriptValue zero = new ScriptValue(0);
        static ScriptValue one  = new ScriptValue(1);

        #region Math Operators
        internal static ScriptValue Add     (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            for (int i = 1; i < aArgs.Length; i++)
            {
                arg1 = aArgs[i].Evaluate();
                arg0.Add( ref arg1 );
            }
            return arg0;
        }
        internal static ScriptValue Subtract(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            for (int i = 1; i < aArgs.Length; i++)
            {
                arg1 = aArgs[i].Evaluate();

                arg0.Subtract( ref arg1 );
            }
            return arg0;
        }
        internal static ScriptValue Multiply(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            for (int i = 1; i < aArgs.Length; i++)
            {
                arg1 = aArgs[i].Evaluate();

                arg0.Multiply(ref arg1);
            }
            return arg0;
        }
        internal static ScriptValue Divide  (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            for (int i = 1; i < aArgs.Length; i++)
            {
                arg1 = aArgs[i].Evaluate();

                arg0.Divide(ref arg1);
            }
            return arg0;
        }

        internal static ScriptValue Sqrt(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            float val = arg0.GetAsFloat();

            arg0.value = (float)Math.Sqrt(val);
            return arg0;
        }
        internal static ScriptValue Sin(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            float val = arg0.GetAsFloat();

            arg0.value = (float)Math.Sin(val);
            return arg0;
        }
        internal static ScriptValue Cos(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            float val = arg0.GetAsFloat();

            arg0.value = (float)Math.Cos(val);
            return arg0;
        }
        internal static ScriptValue Pow(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[0].Evaluate();

            float baseF = arg0.GetAsFloat();
            float expF  = arg1.GetAsFloat();

            arg0.value = (float)Math.Pow(baseF,expF);
            return arg0;
        }
        #endregion

        #region Comparisons
        internal static ScriptValue LargerThan  (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[1].Evaluate();

            if (arg0.LargerThan(ref arg1))
                return one;
            return zero;
        }
        internal static ScriptValue LessThan    (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[1].Evaluate();

            if (arg0.LessThan(ref arg1))
                return one;
            return zero;
        }
        internal static ScriptValue LargerThanEq(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[1].Evaluate();

            if (arg0.LargerThanEq(ref arg1))
                return one;
            return zero;
        }
        internal static ScriptValue LessThanEq  (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[1].Evaluate();

            if (arg0.LessThanEq(ref arg1))
                return one;
            return zero;
        }
        internal static ScriptValue EqualTo     (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[1].Evaluate();

            if (arg0.IsEqual(ref arg1))
                return one;
            return zero;
        }
        internal static ScriptValue NotEqualTo  (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[1].Evaluate();

            if (arg0.IsNotEqual(ref arg1))
                return one;
            return zero;
        }
        internal static ScriptValue And         (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[1].Evaluate();

            if (arg0.IsAnd(ref arg1))
                return one;
            return zero;
        }
        internal static ScriptValue Or          (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();
            arg1 = aArgs[1].Evaluate();

            if (arg0.IsOr(ref arg1))
                return new ScriptValue(1);
            return new ScriptValue(0);
        }
        #endregion

        #region Variables
        internal static ScriptValue Set(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            if (aArgs.Length == 2)
            {
                arg0 = aArgs[1].Evaluate();

                ScriptVariable var = (ScriptVariable)aArgs[0].value.value;
                var.value          = arg0;

                return aArgs[0].value;
            }
            else if (aArgs.Length == 3)
            {
                arg0 = aArgs[2].Evaluate();

                ScriptVariable var  = (ScriptVariable)aArgs[0].value.value;
                PropertyInfo   prop = var.value.value.GetType().GetProperty((string)aArgs[1].value.value);
                prop.SetValue(var.value.value, arg0.value, null);

                return arg0;
            }
            FerrScriptCompiler.Inst.ThrowException("Can only call 'set' with 2 or 3 arguments!");
            return zero;
        }
        internal static ScriptValue Get(CompilerInstruction[] aArgs)
        {
            object obj = null;
            if (aArgs[0].value.type == ScriptValueType.Variable)
            {
                obj = ((ScriptVariable)aArgs[0].value.value).value.value;
            }
            else
                obj = aArgs[0].value.value;

            PropertyInfo property = obj.GetType().GetProperty((string)aArgs[1].value.value);
            object value = property.GetValue(obj, null);

            ScriptValue ret = new ScriptValue(value);
            return ret;
        }
        internal static ScriptValue New(CompilerInstruction[] aArgs)
        {
            object[] args     = new object[aArgs.Length-1];
            Type  [] argTypes = new Type  [aArgs.Length-1];

            // copy the arguments into a format that's friendly with reflection
            for (int i=0;i<aArgs.Length - 1;i++)
            {
                args    [i] = aArgs[i+1].value.value;
                argTypes[i] = args[i].GetType();
            }

            // create an instance of the object
            Type createType = (Type)aArgs[0].value.value;
            object val = createType.GetConstructor(argTypes).Invoke(args);
            //Activator.CreateInstance(createType, args);

            ScriptValue sv = new ScriptValue(0);
            sv.type  = ScriptValueType.Object;
            sv.value = val;

            return sv;
        }
        #endregion

        #region Methods
        internal static ScriptValue Call(CompilerInstruction[] aArgs)
        {
            // standard method call
            if (aArgs[0].value.type == ScriptValueType.Variable)
            {
                ScriptVariable var = (ScriptVariable)aArgs[0].value.value;
                MethodInfo method = var.value.value.GetType().GetMethod((string)aArgs[1].value.value);

                // create the argument list
                object[] callArgs = new object[aArgs.Length - 2];
                for (int i = 2; i < aArgs.Length; i++)
                    callArgs[i - 2] = aArgs[i].value.value;

                // call the method
                return new ScriptValue( method.Invoke(var.value.value, callArgs) );
            }
            // static method call
            else if (aArgs[0].value.type == ScriptValueType.Type)
            {
                Type classType = (Type)aArgs[0].value.value;
                MethodInfo method = classType.GetMethod((string)aArgs[1].value.value);

                // create the argument list
                object[] callArgs = new object[aArgs.Length - 2];
                for (int i = 2; i < aArgs.Length; i++)
                {
                    if (aArgs[i].value.type == ScriptValueType.Variable)
                        callArgs[i - 2] = ((ScriptVariable)aArgs[i].value.value).value.value;
                    else
                        callArgs[i - 2] = aArgs[i].value.value;
                }

                // call the method
                return new ScriptValue(method.Invoke(null, callArgs));
            }
            FerrScriptCompiler.Inst.ThrowException("Can only call 'call' with a variable or a type as the first argument!");
            return zero;
        }
        #endregion

        #region Program Flow
        internal static ScriptValue If   (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            if (arg0.LargerThan(ref zero))
            {
                arg1 = aArgs[1].Evaluate();
                return arg1;
            }
            else if (aArgs.Length > 2)
            {
                arg1 = aArgs[2].Evaluate();;
                return arg1;
            }
            else
                return zero;
        }
        internal static ScriptValue Body (CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            for (int i = 0; i < aArgs.Length; i++)
            {
                arg0 = aArgs[i].Evaluate();
            }

            return arg0;
        }
        internal static ScriptValue While(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            ScriptValue arg1 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            while (arg0.LargerThan(ref zero))
            {
                arg1 = aArgs[1].Evaluate();

                arg0 = aArgs[0].Evaluate();
            }

            return arg1;
        }
        internal static ScriptValue For  (CompilerInstruction[] aArgs)
        {
            ScriptValue  varArg = new ScriptValue(0);
            varArg = aArgs[0].Evaluate();
            ScriptVariable var  = (ScriptVariable)varArg.value;
            ScriptValue    cond = new ScriptValue(0);
            ScriptValue    inc  = new ScriptValue(0);
            ScriptValue    eval = new ScriptValue(0);

            cond = aArgs[1].Evaluate();
            inc  = aArgs[2].Evaluate();

            ScriptValue zero = new ScriptValue(0);

            while (cond.LargerThan(ref zero))
            {
                for (int i = 3; i < aArgs.Length; i++)
                    eval = aArgs[i].Evaluate();

                var.value.Add(ref inc);
                cond = aArgs[1].Evaluate();
            }

            return eval;
        }
        #endregion

        #region Cheating stuff
        static Random r = new Random();
        internal static ScriptValue Rand(CompilerInstruction[] aArgs)
        {
            ScriptValue eval = new ScriptValue((float)r.NextDouble());
            return eval;
        }
        internal static ScriptValue Print(CompilerInstruction[] aArgs)
        {
            ScriptValue arg0 = new ScriptValue(0);
            arg0 = aArgs[0].Evaluate();

            if (arg0.type == ScriptValueType.Variable)
                Console.WriteLine(((ScriptVariable)arg0.value).value.value.ToString());
            else
                Console.WriteLine(arg0.value.ToString());

            return aArgs[0].value;
        }
        #endregion
    }
}
